package ch.tiktak.games.powersudoku;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * A sudoku grid represents a standard 3x3x9 Sudoku grid.
 * It's like a generic grid but with the concept of 9 squares
 */
public class SudokuGrid extends GenericGrid {
    public SudokuGrid() {
        super(9, 9);
    }

    public SudokuGrid(List<int[]> data) {
        super(data);
    }

    public SudokuGrid(GenericGrid anotherGrid) {
        super(anotherGrid);
    }

    /**
     * Returns a square (counting from left to right and from top to bottom)
     *
     * @param squareIndex Square index
     * @return Array of the values using the same reference as above
     */
    public List<Integer> getSquare(int squareIndex) {
        // Define the top left entry of each square
        int topLeftEntryX = 0;
        int topLeftEntryY = 0;

        switch (squareIndex) {
            case 1:
                topLeftEntryX = 1;
                topLeftEntryY = 1;
                break;
            case 2:
                topLeftEntryX = 1;
                topLeftEntryY = 4;
                break;
            case 3:
                topLeftEntryX = 1;
                topLeftEntryY = 7;
                break;
            case 4:
                topLeftEntryX = 4;
                topLeftEntryY = 1;
                break;
            case 5:
                topLeftEntryX = 4;
                topLeftEntryY = 4;
                break;
            case 6:
                topLeftEntryX = 4;
                topLeftEntryY = 7;
                break;
            case 7:
                topLeftEntryX = 7;
                topLeftEntryY = 1;
                break;
            case 8:
                topLeftEntryX = 7;
                topLeftEntryY = 4;
                break;
            case 9:
                topLeftEntryX = 7;
                topLeftEntryY = 7;
                break;
        }

        List<Integer> result = new ArrayList<Integer>();
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                int value = getValue(topLeftEntryX + i, topLeftEntryY + j);
                if (value != -1) {
                    result.add(value);
                }
            }
        }
        return result;
    }

    public int getSquareNumberAt(int rowIndex, int columnIndex) {
        int result;
        if (rowIndex < 4) {
            if (columnIndex < 4) {
                result = 1;
            } else if (columnIndex < 7) {
                result = 2;
            } else {
                result = 3;
            }
        } else if (rowIndex > 3 && rowIndex < 7) {
            if (columnIndex < 4) {
                result = 4;
            } else if (columnIndex < 7) {
                result = 5;
            } else {
                result = 6;
            }
        } else {
            if (columnIndex < 4) {
                result = 7;
            } else if (columnIndex < 7) {
                result = 8;
            } else {
                result = 9;
            }
        }
        return result;
    }

    private List<Integer> getSquareAt(int rowIndex, int columnIndex) {
        return getSquare(getSquareNumberAt(rowIndex, columnIndex));
    }
    /*
    @Override
    public void setValue(int rowIndex, int columnIndex, int dataToInsert) throws InvalidIndexException, InvalidSudokuValueException {
        if (dataToInsert < 1 || dataToInsert > 9) {
            throw new InvalidSudokuValueException(dataToInsert);
        } else {
            super.setValue(rowIndex, columnIndex, dataToInsert);
        }
    }
    */

    /**
     * @param rowIndex    Row index
     * @param columnIndex Column index
     * @param withoutCurrent Remove current number from returned numbers
     * @return Numbers on the same row, without the number at the asked position
     */
    public List<Integer> getRow(int rowIndex, int columnIndex, boolean withoutCurrent) {
        List<Integer> result = new ArrayList<Integer>();
        for (int i = 1; i < 10; i++) {
            if (!withoutCurrent || (i != columnIndex)) {
                int value = getValue(rowIndex, i);
                if (value != -1) {
                    result.add(value);
                }
            }
        }
        return result;
    }

    /**
     * @param rowIndex    Row index
     * @param columnIndex Column index
     * @param withoutCurrent Remove current number from returned numbers
     * @return Numbers on the same column, without the number at the asked position
     */
    public List<Integer> getColumn(int rowIndex, int columnIndex, boolean withoutCurrent) {
        List<Integer> result = new ArrayList<Integer>();
        for (int i = 1; i < 10; i++) {
            if (!withoutCurrent || (i != rowIndex)) {
                int value = getValue(i, columnIndex);
                if (value != -1) {
                    result.add(value);
                }
            }
        }
        return result;
    }

    /**
     * @param rowIndex    Row index
     * @param columnIndex Column index
     * @return values not in the square, nor the row, nor the column
     */
    public List<Integer> getPossibleValues(int rowIndex, int columnIndex) {
        List<Integer> result = new ArrayList<Integer>();
        List<Integer> row = getRow(rowIndex, columnIndex, true);
        List<Integer> column = getColumn(rowIndex, columnIndex, true);
        List<Integer> square = getSquareAt(rowIndex, columnIndex);
        for (int i = 1; i < 10; i++) {
            if (!(row.contains(i) || column.contains(i) || square.contains(i))) {
                result.add(i);
            }
        }
        return result;
    }

    public SudokuGrid copy() {
        SudokuGrid newCopy = new SudokuGrid();
        newCopy.setData(this.getData());
        return newCopy;
    }

    public boolean isValid() {
        boolean valid = true;
        for (int i = 1; i < 10; i++) {
            // Check lines
            if (!hasValidNumbers(getRow(i, 1, false))) {
                valid = false;
                System.out.println("Row " + i + " is not valid.");
            }
            if (!hasValidNumbers(getColumn(1, i, false))) {
                valid = false;
                System.out.println("Column " + i + " is not valid.");
            }
            if (!hasValidNumbers(getSquare(i))) {
                valid = false;
                System.out.println("Square " + i + " is not valid.");
            }
            if (!valid) {
                break;
            }
        }
        return valid;
    }

    public boolean hasValidNumbers(List<Integer> list) {
        boolean valid = true;
        for (int i = 1; i < 10; i++) {
            if (!list.contains(i)) {
                valid = false;
                break;
            }
        }
        return valid;
    }

    public String print() {
        StringBuilder result = new StringBuilder();
        for (int r = 1; r < 10; r++) {
            for (int c = 1; c < 10; c++) {
                int value = getValue(r, c);
                result.append(value == -1 ? "-" : value);
                result.append(" ");
            }
            result.append("\n");
        }
        return result.toString();
    }

    public static void main(String[] args) {
        try {
            CSVGridReader reader = new CSVGridReader("/home/mathieu/dev/test/tinytools/sudoku-solver/src/grid1b-solved.csv");
            SudokuGrid grid = new SudokuGrid();
            grid.setData(reader.getEntries());
            System.out.println(grid.isValid() ? "Grid is valid." : "Grid is NOT valid.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
